#include <os/list.h>
#include <os/mm.h>
#include <os/lock.h>
#include <os/sched.h>
#include <os/time.h>
#include <os/irq.h>
#include <screen.h>
#include <stdio.h>
#include <assert.h>

pcb_t pcb[NUM_MAX_TASK];
const ptr_t pid0_stack = INIT_KERNEL_STACK + PAGE_SIZE;
pcb_t pid0_pcb = {
    .pid = 0,
    .kernel_sp = (ptr_t)pid0_stack,
    .user_sp = (ptr_t)pid0_stack,
    .preempt_count = 0,
    .status = TASK_RUNNING,
};

LIST_HEAD(ready_queue);

/* current running task PCB */
pcb_t * volatile current_running;

/* global process id */
pid_t process_id = 0;

void do_scheduler(void)
{
    // TODO schedule
    // Modify the current_running pointer.
    pcb_t *next_running, *tmp;
    list_node_t *tmp_node;

    uint64_t ticks;
    ticks = get_ticks();

    tmp_node = ready_queue.prev;
    next_running = list_entry(tmp_node, pcb_t, list);
    while(next_running->status == TASK_BLOCKED){
        tmp_node = tmp_node->prev;
        next_running = list_entry(tmp_node, pcb_t, list);
    }
    tmp_node = ready_queue.next;
    while(tmp_node != &(ready_queue)){
        tmp = list_entry(tmp_node, pcb_t, list);
        tmp->priority += 1;
        tmp_node = tmp_node->next;
    }
    next_running->priority = next_running->task_priority;
    list_del(&(next_running->list));
    list_add_prior(&(next_running->list));
    if(current_running->status == TASK_RUNNING)
        current_running->status = TASK_READY;
    next_running->status = TASK_RUNNING;
    current_running = next_running;
    process_id = current_running->pid;
    
    int x = current_running->cursor_x;
    int y = current_running->cursor_y;
    vt100_move_cursor(1, 8);
    ticks = get_ticks() - ticks;
    printk("do_scheduler spend: %u ticks\n",ticks);
    printk("priority: %d", current_running->task_priority);
    // restore the current_runnint's cursor_x and cursor_y
    vt100_move_cursor(x, y);
    screen_cursor_x = current_running->cursor_x;
    screen_cursor_y = current_running->cursor_y;

}

void do_sleep(uint32_t sleep_time)
{
    // TODO: sleep(seconds)
    // note: you can assume: 1 second = `timebase` ticks
    // 1. block the current_running
    // 2. create a timer which calls `do_unblock` when timeout
    // 3. reschedule because the current_running is blocked.
    current_running->status = TASK_BLOCKED;
    timer_create((TimerCallback)&do_unblock, &(current_running->list), (uint64_t)sleep_time*time_base);
    do_scheduler();
}

void do_block(list_node_t *pcb_node, list_head *queue)
{
    // TODO: block the pcb task into the block queue
    list_del(pcb_node);
    list_add_tail(pcb_node, queue);
    current_running->status = TASK_BLOCKED;
    do_scheduler();
}

void do_unblock(list_node_t *pcb_node)
{
    // TODO: unblock the `pcb` from the block queue
    pcb_t* tmp;
    list_del(pcb_node);
    list_add_prior(pcb_node);
    tmp = list_entry(pcb_node, pcb_t, list);
    tmp->status = TASK_READY;
}

 void list_add_prior(list_node_t *pcb_node){
    list_node_t *next;
    pcb_t *next_pcb, *tmp;
    tmp = list_entry(pcb_node, pcb_t, list);
    if(list_empty(&ready_queue)){
        list_add(pcb_node, &ready_queue);
        return ;
    }
    next = ready_queue.prev;
    next_pcb = list_entry(next, pcb_t, list);
    while(next_pcb->priority >= tmp->priority){
        if(next->prev != &(ready_queue))
          next = next->prev;
        else{
          list_add(pcb_node, &ready_queue);
          return ;
        }
        next_pcb = list_entry(next, pcb_t, list);
    }
    list_add(pcb_node, next);
}